﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DDD {

    public abstract class IndexBuffer : Object {
        public abstract PrimitiveType PrimitiveType { get; }
        public abstract int PrimitiveCount { get; }
        public abstract int Cpp { get; }
        public abstract IndexType IndexType { get; }
        public abstract int IndexCount { get; }
    }

    /// <summary>
    /// インデックスバッファー
    /// </summary>
    /// <remarks>
    /// インデックスは頂点と頂点を結びプリミティブを形成します。
    /// プリミティブには点(Point), 線(Line), トライアングル(Triangle)の3種類があります。
    /// 4角形およびそれ以上の多角形には対応しません。
    /// </remarks>
    /// <typeparam name="T">インデックスの型</typeparam>
    public class IndexBuffer<T> : IndexBuffer, IEnumerable<T> where T : struct {

        #region Field
        PrimitiveType primitiveType;
        int primitiveCount;
        int cpp;
        IndexType indexType;
        T[] indices;
        #endregion

        #region Constructor

        /// <summary>
        /// コンストラクター
        /// </summary>
        /// <remarks>
        /// 指定のインデックスを持つインデックスバッファーを作成します。
        /// インデックスは直ちに内部にコピーされこの関数が返った後は必要ありません。
        /// 作成したインデックスは後から変更できないのが仕様です。
        /// </remarks>
        /// <param name="type">プリミティブのタイプ</param>
        /// <param name="num">プリミティブ数</param>
        public IndexBuffer (PrimitiveType type, int num) {
            if (num <= 0) {
                throw new ArgumentException ("Number of primitives is invalid");
            }
            if (!(typeof (T) == typeof (byte) || typeof(T) == typeof(sbyte) ||
                typeof (T) == typeof (short) || typeof (T) == typeof (ushort) ||
                typeof (T) == typeof (int) || typeof (T) == typeof (uint))) {
                throw new ArgumentException ("Index type is invalid");
            }
            this.primitiveType = type;
            this.primitiveCount = num;
            this.cpp = type.Cpp ();
            switch (typeof (T).Name) {
                case "Byte": this.indexType = IndexType.Byte; break;
                case "SByte": this.indexType = IndexType.SByte; break;
                case "Int16": this.indexType = IndexType.Short; break;
                case "UInt16": this.indexType = IndexType.UShort; break;
                case "Int32": this.indexType = IndexType.Int; break;
                case "UInt32": this.indexType = IndexType.UInt; break;
                default: throw new NotImplementedException("Sorry");
            }
            this.indices = new T[num * cpp];
        }
        #endregion

        #region Property

        public override PrimitiveType PrimitiveType {
            get { return primitiveType; }
        }

        public override int PrimitiveCount {
            get { return primitiveCount; }
        }

        public override int Cpp {
            get { return cpp; }
        }

        public override IndexType IndexType {
            get { return indexType; } 
        }

        public override int IndexCount {
            get { return indices.Length; }
        }

        public IEnumerable<T> Indices {
            get { return indices; }
        }
  
        #endregion

        #region Method
        /// <summary>
        /// 全てのインデックスを列挙する列挙子
        /// </summary>
        /// <returns></returns>
        IEnumerator<T> IEnumerable<T>.GetEnumerator () {
            return ((IEnumerable<T>)indices).GetEnumerator ();
        }

        /// <summary>
        /// 全てのインデックスを列挙する列挙子
        /// </summary>
        /// <returns></returns>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () {
            return indices.GetEnumerator ();
        }


        /// <summary>
        /// インデックスを取得します
        /// </summary>
        /// <param name="firstPrimitive">取得を始める最初のプリミティブインデックス</param>
        /// <param name="numPrimitives">取得するプリミティブの数</param>
        /// <param name="dst">結果を書き込む配列</param>
        public void Get (int firstPrimitive, int numPrimitives, T[] dst) {
            if (firstPrimitive < 0 || firstPrimitive >= PrimitiveCount) {
                throw new ArgumentException ("First primitive is invalid");
            }
            if (numPrimitives < 0 || firstPrimitive + numPrimitives > PrimitiveCount) {
                throw new ArgumentException ("Number of primitives is inalid");
            }
            if (dst == null) {
                return;
            }
            if (dst.Length < numPrimitives * Cpp) {
                throw new ArgumentException ("Dst is too short");
            }

            Array.Copy (this.indices, firstPrimitive * Cpp, dst, 0, numPrimitives * Cpp);
        }

        /// <summary>
        /// インデックスをセットします
        /// </summary>
        /// <param name="firstPrimitive">書き込みを始める最初のプリミティブインデックス</param>
        /// <param name="numPrimitives">書き込みするプリミティブの数</param>
        /// <param name="src">データを読み込む配列</param>
        public void Set (int firstPrimitive, int numPrimitives, T[] src) {
            if (firstPrimitive < 0 || firstPrimitive >= PrimitiveCount) {
                throw new ArgumentException ("First primitive is invalid");
            }
            if (numPrimitives < 0 || firstPrimitive + numPrimitives > PrimitiveCount) {
                throw new ArgumentException ("Number of primitives is inalid");
            }
            if (src == null) {
                throw new ArgumentNullException ("Src is null");
            }
            if (src.Length < numPrimitives * Cpp) {
                throw new ArgumentException ("Src is too short");
            }

            Array.Copy (src, 0, this.indices, firstPrimitive * Cpp, numPrimitives * Cpp);
        }

        /// <summary>
        /// 現在のオブジェクトの状態を表す文字列を返します。
        /// </summary>
        /// <returns>現在のオブジェクトを説明する文字列</returns>
        public override string ToString () {
            return string.Format ("{0}:{1}", PrimitiveType, PrimitiveCount);
        }
        #endregion


    }
}
